/*
 * Copyright 2014 JootMir Project
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * Support: http://www.jootmir.org
 */
package org.jootmir.client.core.texture.loader;

import java.util.Iterator;
import java.util.Stack;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;

import org.apache.log4j.Logger;
import org.jootmir.client.core.AllCache;
import org.jootmir.client.core.Application;
import org.jootmir.client.core.exception.JMir2Exception;

/**
 * 纹理读取类
 * <br>
 * 此类可实例化，类实例线程安全<b>使用多个类实例实现多线程加载以提升速度</b>
 * <br>
 * 请逐帧调用{@link #update() update}函数，如果不调用，我们不会进行纹理读取:(
 * <br>
 * XXX 最新代码中您不需要逐帧调用{@link #update() update}函数，我们已经将此函数调用自动化到{@link Application#mainLoop() mainLoop}中。
 * <b>已经完成的任务会被暂停，但当重新使用{@link #load(Integer) load}函数时它会自行开始任务执行过程</b>
 * <br>
 * 值得一提的是您最好使用{@link #dispose() dispose}方法释放资源，因为在加载完成后会有一个线程驻停内存
 * <br>
 * 此类默认使用异步加载方式
 * <br>
 * 使用{@link #getProgress() progress}获取加载进度或使用{@link #finishLoading() finish}阻塞方式完成资源加载
 * <br>
 * 如果希望获取加载的资源，请从{@link AllCache#getTexture(Integer) cache}中获取
 * <br>
 * 停止使用此类时调用{@link #dispose() dispose}方法销毁实例
 * <br>
 * 当然如果希望立即停止加载，但不希望销毁实例的话可以使用{@link #clear() clear}方法
 * <b>clear会阻塞线程到当前任务队列完全成功，因此可以使用此方法“清理垃圾”，即程序运行过久时产生的一些碎片(如较大的{@link #loaded loaded})</b>
 * <br>
 * <b>如果没有设置{@link #setErrorListener(TextureLoadeErrorListener) errorListener}的话可能会抛出异常</b>
 *  
 * @author ShawRyan
 */
public class TextureLoader {
	/** log */
	private static final Logger log = Logger.getLogger(TextureLoader.class);
	/** 缓存支持 */
	private AllCache cache = AllCache.get();
	
	/** 已经加载的纹理数量 */
	private int loaded = 0;
	/** 需要加载的纹理数量 */
	private int toLoad = 0;
	
	/** 任务队列 */
	private Stack<TextureLoadingTask> tasks = new Stack<TextureLoadingTask>();
	/** 加载异常监听器 */
	private TextureLoadeErrorListener listener = null;
	/** 线程池 */
	private final ExecutorService threadPool;
	/** 加载器信息 */
	private Class<? extends TextureLoaderHandler> handlerClz;

	/** 封锁构造函数<br>我也不知道为什么要做样做<br>先就这样吧…… */
	private TextureLoader(Class<? extends TextureLoaderHandler> clz) {
		handlerClz = clz;
		threadPool = Executors.newFixedThreadPool(1, new ThreadFactory() {
			
			@Override
			public Thread newThread(Runnable r) {
				Thread thread = new Thread(r, "TextureLoader-Load-Thread");
				thread.setDaemon(true);
				return thread;
			}
		});
		//cache.putTextureLoader(this); // 没有必要立即放入队列，在第一次load后进行
	}
	/**
	 * 使用无参构造函数创建一个类的新实例并返回
	 * 
	 * @param clz
	 * 		纹理加载器类，同一个加载器只能加载同一种纹理
	 * @return 类的新实例
	 */
	public static TextureLoader newInstance(Class<? extends TextureLoaderHandler> clz) {
		return new TextureLoader(clz);
	}
	
	/**
	 * 把纹理加载放入任务队列
	 * 
	 * @param index
	 * 		纹理索引
	 */
	public synchronized void load(Integer index) {
		if (tasks.size() == 0) {
			loaded = 0;
			toLoad = 0;
		}
		// 检查加载队列
		for(int i = 0; i < tasks.size(); ++i) {
			if(tasks.get(i).getIndex() == index) return;
		}
		// 检查已加载资源
		if(cache.getTexture(index) != null) return;
		toLoad++;
		// 加入任务队列
		if(cache.getTextureLoaderHandler(index) != null) {
			tasks.push(new TextureLoadingTask(index, cache.getTextureLoaderHandler(index), threadPool));
		} else {
			tasks.push(new TextureLoadingTask(index, handlerClz, threadPool));
		}
		// 放入缓存支持类
		// 因为可能已经被remove
		// 虽然toLoad为1可能是较多加载任务完成了导致，但并不影响效率。如果每次load都尝试加入队列开销更大
		if(toLoad == 1)
			cache.putTextureLoader(this);
	}
	
	/**
	 * 进行纹理加载的操作
	 * <br>
	 * 请逐帧调用
	 * 
	 * @return 如果所有需要加载的纹理都加载了则返回true，反之返回false
	 */
	public synchronized boolean update() {
		try {
			if(tasks.size() == 0) return true;
			return updateTask() && tasks.size() == 0;
		} catch (Throwable t) {
			tasks.clear();
			if (listener != null) {
				listener.error(tasks.pop().getIndex(), t);
				return tasks.size() == 0;
			} else {
				JMir2Exception exception = JMir2Exception.valueOf(t, JMir2Exception.EXCEPTION_TYPE_FILESTREAM);
				log.error(exception.getMessage(), t);
				throw exception;
			}
		}
	}
	/** 更新当前执行的任务状态<br>XXX遍历任务队列，不限于每帧只加载一个纹理 */
	private boolean updateTask() {
		boolean result = false;
		Iterator<TextureLoadingTask> itTasks = tasks.iterator();
		while(itTasks.hasNext()) {
			TextureLoadingTask task = itTasks.next();
			if (task.update()) {
				Integer idx = task.getIndex();
				if(cache.getTextureLoaderHandler(idx) == null)
					cache.putTextureLoaderHandler(idx, task.getImageInfo());
				cache.putTexture(idx, task.getTex());
				loaded++;
				itTasks.remove();
				result = true;
			}
		}
		return result;
	}
	
	/** 阻塞线程等待所有加载完成 */
	public void finishLoading() {
		while (!update())
			Thread.yield();
	}
	/** 获取加载百分比 */
	public float getProgress() {
		if (toLoad == 0) return 1;
		return Math.min(1, loaded / (float)toLoad);
	}
	/** 清除任务队列 */
	public void clear() {
		while (!update())
			;
		this.loaded = 0;
		this.toLoad = 0;
		this.tasks.clear();
	}
	/** 销毁实例 */
	public void dispose() {
		threadPool.shutdown();
		try {
			threadPool.awaitTermination(Long.MAX_VALUE, TimeUnit.SECONDS);
		} catch (InterruptedException ex) {
			JMir2Exception exception = JMir2Exception.valueOf(ex, JMir2Exception.EXCEPTION_TYPE_CALC);
			log.error(exception.getMessage(), ex);
			throw exception;
		}
	}
	/** 设置错误监听 */
	public synchronized void setErrorListener (TextureLoadeErrorListener listener) {
		this.listener = listener;
	}
}